# -*- coding: utf-8 -*-

"""
Moduł odpowiedzialny za przeprowadzanie operacji na wektorach a także za
obliczanie wektora prędości i grawitacji.

B{Moduł zawiera:}
    1. Klasa Vector
        - Metoda __init__
        - Metoda setVector
        - Metoda getVx
        - Metoda getVy
        - Metoda setVx
        - Metoda setVy
        - Metoda getVectorLength
        - Metoda addVector
        - Metoda substractVector
        - Metoda multiplyVector
        - Metoda calculateCrossProduct
        - Metoda calculateDotProduct
        - Metoda dot
        - Metoda rotateVector
        - Metoda lengthenVector
        - Metoda shortenVector
        - Metoda calculateAngleBetweenVector
        - Metoda getVectorAngle
        - Metoda normalize
    2. Klasa Velocity
        - Metoda __init__
    3. Klasa Gravity
        - Metoda __init__
"""

from __future__ import unicode_literals
from math import sin, cos, acos, radians, sqrt
from random import randint



class Vector(object):    
    """
    Klasa reprezentująca wektor w układzie współrzędnych. 
    
    Posiada dwa główne atrybuty symbolizujące składowe wektora, na które można
    rozłożyć każdy wektor w układzie współrzędnych OXY. Znaki składowych
    oznaczają ich kierunek w układzie współrzędnych. Gdy składowa X ma 
    wartość dodatnią, to jej wektor jest skierowany w prawo,
    a gdy ujemną - w lewo. Natomiast gdy składowa Y ma wartość dodatnią,
    to jej wektor jest skierowany w górę, a gdy ujemną - w dół.
    """
    
    
    def __init__(self, Vx, Vy):
        """
        Konstruktor obiektu wektora ustawia początkowe wartości obu
        składowych wektora.
        
        @param Vx: Parametr określający wartość składowej prędkości na osi X.
        @param Vy: Parametr określający wartość składowej prędkości na osi Y.
        """
        
        self._Vx = Vx
        self._Vy = Vy
    
    
    def setVector(self, Vx, Vy):
        """
        Procedura ustawiająca podane wartości składowych wektora.
        
        @param Vx: Parametr określający wartość składowej prędkości na osi X.
        @param Vy: Parametr określający wartość składowej prędkości na osi Y.
        """
        
        self._Vx = Vx
        self._Vy = Vy
    
    
    def getVx(self):
        """
        Funkcja zwracająca składową X wektora.
        
        @return: Składowa wektora na osi X.
        """
        
        return self._Vx
    
    
    def getVy(self):
        """
        Funkcja zwracająca składową Y wektora.
        
        @return: Składowa wektora na osi Y.
        """
        
        return self._Vy
    
    
    def setVx(self, NewVx):
        """
        Procedura ustawiająca składową X wektora.
        
        @param NewVx: Parametr określający nową wartość składowej prędkości na osi X.
        """
        
        self._Vx = NewVx
    
    
    def setVy(self, NewVy):
        """
        Procedura ustawiająca składową Y wektora.
        
        @param NewVy: Parametr określający nową wartość składowej prędkości na osi Y.
        """
        
        self._Vy = NewVy
    
    
    def getVectorLength(self):
        """
        Funkcja zwracająca długość wektora.
        
        @return: Długość wektora:
            M{sqrt(Vx^2+Vy^2)}
        """
        
        return sqrt(self.getVx()**2 + self.getVy()**2)
    
    
    def addVector(self, AnotherVector):
        """
        Procedura dodająca do aktualnych wartości wektora składowe nowego
        wektora, podanego w parametrach.
        
        Wynikiem jest wektor, będący wypadkową dwóch wektorów.
        
        @param AnotherVector: Parametr określający nowy wektor.
        """
        
        #AnotherVector.__class__ = Vector
        NewVx = self.getVx() + AnotherVector.getVx()
        NewVy = self.getVy() + AnotherVector.getVy()
        
        self.setVector(NewVx, NewVy)
    
    
    def substractVector(self, AnotherVector):
        """
        Procedura odejmująca od aktualnych wartości wektora składowe nowego
        wektora, podanego w parametrach.
        
        Wynikiem jest wektor, będący różnicą dwóch wektorów.
        
        @param AnotherVector: Parametr określający nowy wektor.
        """
        
        NewVx = self.getVx() - AnotherVector.getVx()
        NewVy = self.getVy() - AnotherVector.getVy()
        
        self.setVector(NewVx, NewVy)
    
    
    def multiplyVector(self, Factor):
        """
        Procedura mnożąca wektor przez podany w parametrze czynnik.
        
        @param Factor: Parametr określający wartość mnożnika.
        """
        
        NewVx = self.getVx() * Factor
        NewVy = self.getVy() * Factor
        
        self.setVector(NewVx, NewVy)
    
    
    def calculateCrossProduct(self, anotherVector):
        """
        Funkcja obliczająca iloczyn wektorowy dla wektora na rzecz którego
        została wywołana i wektora podanego w parametrze.
        
        @param AnotherVector: Parametr określający nowy wektor.
        
        @return: Iloczyn wektorowy: M{V1x*V2y-V1y*V2x}
        """
        
        return self.getVx()*anotherVector.getVy() - self.getVy()*anotherVector.getVx()
    
    
    def calculateDotProduct(self, anotherVector):
        """
        Funkcja obliczająca iloczyn skalarny dla wektora na rzecz którego
        została wywołana i wektora podanego w parametrze.
        
        Funkcja oblicza iloczyn skalarny na podstawie długości wektorów
        i kąta pomiędzy nimi.
        
        @param AnotherVector: Parametr określający nowy wektor.
        
        @return: Iloczyn skalarny: M{|V1|*|V2|*cos(S{ang})}
        """
        
        angle = self.calculateAngleBetweenVector(anotherVector)
        
        return self.getVectorLength() * anotherVector.getVectorLength() * cos(angle)
    
    
    def dot(self, anotherVector):
        """
        Funkcja obliczająca iloczyn skalarny dla wektora na rzecz którego
        została wywołana i wektora podanego w parametrze.
        
        Funkcja oblicza iloczyn skalarny na podstawie współrzędnych wektorów.
        
        @param AnotherVector: Parametr określający nowy wektor.
        
        @return: Iloczyn skalarny: M{V1x*V2x+V1y*V2y}
        """
        
        return self.getVx() * anotherVector.getVx() + self.getVy() * anotherVector.getVy()
    
    
    def rotateVector(self, rotationAngle):
        """
        Procedura obracająca wektor o podany w parametrze kąt w stopniach.
        
        @param rotationAngle: Parametr określający kąt obrotu w [*].
        """
        
        Radians = radians(rotationAngle)
        
        S = sin(Radians)
        C = cos(Radians)
        
        Vx = C * self.getVx() - S * self.getVy()
        Vy = S * self.getVx() + C * self.getVy()
        
        self.setVector(Vx, Vy)
    
    
    def lengthenVector(self, value):
        """
        Procedura wydłużająca wektor o podaną wartość.
        
        @param Value: Parametr określający o ile ma zostać przedłużony wektor.
        """
        
        NewVx = (self.getVx() * (self.getVectorLength()+value)) / self.getVectorLength()
        NewVy = (self.getVy() * (self.getVectorLength()+value)) / self.getVectorLength()
        
        self.setVector(NewVx, NewVy)
    
    
    def shortenVector(self, value):
        """
        Procedura skracająca wektor o podaną wartość.
        
        @param Value: Parametr określający o ile ma zostać skrócony wektor.
        """
        
        NewVx = (self.getVx() * (self.getVectorLength()-value)) / self.getVectorLength()
        NewVy = (self.getVy() * (self.getVectorLength()-value)) / self.getVectorLength()
        
        self.setVector(NewVx, NewVy)
    
    
    def calculateAngleBetweenVector(self, anotherVector):
        """
        Funkcja obliczająca kąt między dwoma wektorami.
        
        Obliczony kąt podany jest w radianach.
        
        @param AnotherVector: Parametr określający nowy wektor.
        
        @return: Funkcja zwraca wartość kąta pomiędzy wektorami:
            1. Gdy wartość wyrażenia M{(V1S{omicron}V2)/(V1*V2)} jest mniejsza 
            niż -1 to funkcja zwraca Arccos(-1).
            2. Gdy wartość wyrażenia M{(V1S{omicron}V2)/(V1*V2)} jest większa 
            niż 1 to funkcja zwraca Arccos(1). 
        """
        
        # TODO: przypadek wektora o długości (0,0); division by 0
        
        #print("vectorLength = {0}".format(self.getVectorLength()))
        #print("anotherVectorLength = {0}".format(anotherVector.getVectorLength()))
        
        ScalarProduct = (self.getVx()*anotherVector.getVx() + self.getVy()*anotherVector.getVy())
        SelfMagnitude = sqrt(self.getVx()**2 + self.getVy()**2)
        AnotherVectorMagnitude = sqrt(anotherVector.getVx()**2 + anotherVector.getVy()**2)
        
        value = ScalarProduct/(SelfMagnitude*AnotherVectorMagnitude)
        
        if value < -1:
            value = -1
        if value > 1:
            value = 1
        
        return acos(value)
    
    
    def getVectorAngle(self):
        """
        Funkcja zwracająca kąt wektora w odniesieniu do osi OX.
        
        @return: Wartość kąta pomiędzy wektorem a osią X.
        """
        
        VOX = Vector(1,0)
        
        return self.calculateAngleBetweenVector(VOX)
    
    
    def normalize(self):
        """
        Funkcja zwracająca wektor znormalizowany.
        
        @return:
            1. Jeżeli długość wektora jest różna od 0 wtedy funkcja zwraca
            wektor znormalizowany dzieląc jego współrzędne przez jego długość.
            2. Jeżeli długość wektora jest równa O wtedy funkcja zwraca wektor
            zerowy.
        """
        
        len = self.getVectorLength()
        
        if len != 0.0:
            self.setVx(self.getVx() / len);
            self.setVy(self.getVy() / len);
        else:
            self.setVx(0.0)
            self.setVy(0.0)

        return self



class Velocity(Vector):
    """
    Klasa reprezentująca prędkość obiektu.
    
    Posiada dwa główne atrybuty symbolizujące składowe wektora prędkości,
    na które można rozłożyć każdy wektor w układzie współrzędnych OXY.
    """


    def __init__(self, Vx = 0, Vy = 0):
        """
        Konstruktor obiektu wektora ustawia początkowe wartości obu
        składowych wektora.
        
        Jeśli nie podano początkowych wartości składowych wyznaczane są one 
        losowo zgodnie z minimalną i maksymalną wartością składowych wektora
        prędkości podanych w opcjach symulacji.
        
        @param Vx: Parametr określający wartość składowej prędkości na osi X.
        @param Vy: Parametr określający wartość składowej prędkości na osi Y.
        """
        
        #Vector.__init__(self, Vx, Vy)
        super(Velocity, self).__init__(Vx, Vy)



class Gravity(Vector):
    """
    Klasa reprezentująca wektor grawitacji - wektor długości 1 o początku w
    punkcie przyłożenia i kącie obrotu podanym jako GravityAngle.
    """

    
    def __init__(self, GravityAngle, GravitationalAcceleration):
        """
        Konstruktor ustawia długość wektora grawitacji na 1 i wyznacza jego
        koniec na podstawie kąta obrotu podanego w parametrze.
        
        Kąt obrotu - zgodnie z numeracją ćwiartek układu współrzędnych.
        
        @param GravityAngle: Parametr odpowiadający za wartość kąta padania
        grawitacji. Domyślnie 270[*].
        @param GravitationalAcceleration: Parametr odpowiadający za wartość
        przyśpieszenia ziemskiego, wynosi ono 9.80665 [m/s^2].
        """
        
        InitialVx = 1
        InitialVy = 0
        
        
        super(Gravity, self).__init__(InitialVx, InitialVy)
        #Vector.__init__(self, Vx, Vy)
        
        self.rotateVector(GravityAngle)
        self.multiplyVector(GravitationalAcceleration)
        



